{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "a02867dd",
   "metadata": {},
   "source": [
    "# 사용자 정의 Evaluator 로 평가\n",
    "\n",
    "사용자 정의 LLM 평가자를 구성하거나 Heuristic 평가자를 구성할 수 있습니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "34a30a84",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 설치\n",
    "# !pip install -U langsmith langchain-teddynote"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d75d1492",
   "metadata": {},
   "outputs": [],
   "source": [
    "# API KEY를 환경변수로 관리하기 위한 설정 파일\n",
    "from dotenv import load_dotenv\n",
    "\n",
    "# API KEY 정보로드\n",
    "load_dotenv()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "633d9db2",
   "metadata": {},
   "outputs": [],
   "source": [
    "# LangSmith 추적을 설정합니다. https://smith.langchain.com\n",
    "# !pip install -qU langchain-teddynote\n",
    "from langchain_teddynote import logging\n",
    "\n",
    "# 프로젝트 이름을 입력합니다.\n",
    "logging.langsmith(\"CH16-Evaluations\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a09c8411",
   "metadata": {},
   "source": [
    "## RAG 성능 테스트를 위한 함수 정의"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "011f17eb",
   "metadata": {},
   "source": [
    "테스트에 활용할 RAG 시스템을 생성하겠습니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8a79916b",
   "metadata": {},
   "outputs": [],
   "source": [
    "from myrag import PDFRAG\n",
    "from langchain_openai import ChatOpenAI\n",
    "\n",
    "# PDFRAG 객체 생성\n",
    "rag = PDFRAG(\n",
    "    \"data/SPRI_AI_Brief_2023년12월호_F.pdf\",\n",
    "    ChatOpenAI(model=\"gpt-4o-mini\", temperature=0),\n",
    ")\n",
    "\n",
    "# 검색기(retriever) 생성\n",
    "retriever = rag.create_retriever()\n",
    "\n",
    "# 체인(chain) 생성\n",
    "chain = rag.create_chain(retriever)\n",
    "\n",
    "# 질문에 대한 답변 생성\n",
    "chain.invoke(\"삼성전자가 자체 개발한 생성형 AI의 이름은 무엇인가요?\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "75b71e6e",
   "metadata": {},
   "source": [
    "`ask_question` 이라는 이름으로 함수를 생성합니다. 입력으로는 `inputs` 라는 딕셔너리를 받고, 출력으로는 `answer` 라는 딕셔너리를 반환합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a922c6d7",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 질문에 대한 답변하는 함수를 생성\n",
    "def ask_question(inputs: dict):\n",
    "    return {\"answer\": chain.invoke(inputs[\"question\"])}"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1543dcaa",
   "metadata": {},
   "source": [
    "## 사용자 정의 Evaluator 구성\n",
    "\n",
    "아래의 사용자 정의 함수의 입력 매개변수와 반환 값 형식을 지켜서 생성할 수 있습니다.\n",
    "\n",
    "**사용자 정의 함수**\n",
    "\n",
    "- 입력으로는 `Run` 과 `Example` 을 받고 출력으로는 `dict` 를 반환합니다.\n",
    "- 반환 값은 `{\"key\": \"score_name\", \"score\": score}` 형식으로 구성됩니다."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "093ac7b4",
   "metadata": {},
   "source": [
    "아래는 간단한 예시 함수를 정의하였습니다. 답변에 상관없이 1~10 사이의 랜덤 점수를 반환합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "24d00cd7",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langsmith.schemas import Run, Example\n",
    "import random\n",
    "\n",
    "\n",
    "def random_score_evaluator(run: Run, example: Example) -> dict:\n",
    "    # 랜덤 점수 반환\n",
    "    score = random.randint(1, 10)\n",
    "    return {\"key\": \"random_score\", \"score\": score}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6b6f0980",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langsmith.evaluation import evaluate\n",
    "\n",
    "# 데이터셋 이름 설정\n",
    "dataset_name = \"RAG_EVAL_DATASET\"\n",
    "\n",
    "# 실행\n",
    "experiment_results = evaluate(\n",
    "    ask_question,\n",
    "    data=dataset_name,\n",
    "    evaluators=[random_score_evaluator],\n",
    "    experiment_prefix=\"CUSTOM-EVAL\",\n",
    "    # 실험 메타데이터 지정\n",
    "    metadata={\n",
    "        \"variant\": \"랜덤 점수 평가자\",\n",
    "    },\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b17069a8",
   "metadata": {},
   "source": [
    "![](./assets/output-07.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d59ec76a",
   "metadata": {},
   "source": [
    "## Custom LLM-as-Judge "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bcc409a7",
   "metadata": {},
   "source": [
    "이번에는 LLM Chain 을 만들어서 평가자로 활용하겠습니다.\n",
    "\n",
    "먼저, `context`, `answer`, `question` 을 반환하는 함수를 정의합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e21e16ce",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Context 를 반환하는 RAG 결과 반환 함수\n",
    "def context_answer_rag_answer(inputs: dict):\n",
    "    context = retriever.invoke(inputs[\"question\"])\n",
    "    return {\n",
    "        \"context\": \"\\n\".join([doc.page_content for doc in context]),\n",
    "        \"answer\": chain.invoke(inputs[\"question\"]),\n",
    "        \"question\": inputs[\"question\"],\n",
    "    }"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2bd1c35f",
   "metadata": {},
   "source": [
    "다음으로는 사용자 정의 LLM 평가자를 생성합니다.\n",
    "\n",
    "이때 평가 프롬프트는 자유롭게 조절 가능합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "38358da4",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain import hub\n",
    "\n",
    "# 평가자 Prompt 가져오기\n",
    "llm_evaluator_prompt = hub.pull(\"teddynote/context-answer-evaluator\")\n",
    "llm_evaluator_prompt.pretty_print()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cafc9630",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.output_parsers import StrOutputParser\n",
    "from langchain_openai import ChatOpenAI\n",
    "\n",
    "# 평가자 생성\n",
    "custom_llm_evaluator = (\n",
    "    llm_evaluator_prompt\n",
    "    | ChatOpenAI(temperature=0.0, model=\"gpt-4o-mini\")\n",
    "    | StrOutputParser()\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5d8f45be",
   "metadata": {},
   "source": [
    "이전에 생성한 `context_answer_rag_answer` 함수를 사용하여 생성한 답변, 문맥을 `custom_llm_evaluator` 에 입력하여 평가를 진행합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3b7829e4",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 답변을 생성합니다.\n",
    "output = context_answer_rag_answer(\n",
    "    {\"question\": \"삼성전자가 자체 개발한 생성형 AI의 이름은 무엇인가요?\"}\n",
    ")\n",
    "\n",
    "# 점수 평가 실행\n",
    "custom_llm_evaluator.invoke(output)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5f83d040",
   "metadata": {},
   "source": [
    "`custom_evaluator` 함수를 정의합니다.\n",
    "\n",
    "- `run.outputs`: RAG 체인이 생성한 answer, context, question 을 가져옵니다.\n",
    "- `example.outputs`: 데이터셋의 정답 답변을 가져옵니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "579dab77",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langsmith.schemas import Run, Example\n",
    "\n",
    "\n",
    "def custom_evaluator(run: Run, example: Example) -> dict:\n",
    "    # LLM 생성 답변, 정답 답변 가져오기\n",
    "    llm_answer = run.outputs.get(\"answer\", \"\")\n",
    "    context = run.outputs.get(\"context\", \"\")\n",
    "    question = example.outputs.get(\"question\", \"\")\n",
    "\n",
    "    # 랜덤 점수 반환\n",
    "    score = custom_llm_evaluator.invoke(\n",
    "        {\"question\": question, \"answer\": llm_answer, \"context\": context}\n",
    "    )\n",
    "    return {\"key\": \"custom_score\", \"score\": float(score)}"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d30b9219",
   "metadata": {},
   "source": [
    "평가를 진행합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a7b91a23",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langsmith.evaluation import evaluate\n",
    "\n",
    "# 데이터셋 이름 설정\n",
    "dataset_name = \"RAG_EVAL_DATASET\"\n",
    "\n",
    "# 실행\n",
    "experiment_results = evaluate(\n",
    "    context_answer_rag_answer,\n",
    "    data=dataset_name,\n",
    "    evaluators=[custom_evaluator],\n",
    "    experiment_prefix=\"CUSTOM-LLM-EVAL\",\n",
    "    # 실험 메타데이터 지정\n",
    "    metadata={\n",
    "        \"variant\": \"Custom LLM Evaluator 활용한 평가\",\n",
    "    },\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0ec5ea44",
   "metadata": {},
   "source": [
    "![](./assets/output-08.png)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "langchain-kr-lwwSZlnu-py3.11",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
